text = 'QQQA23B23'
pattern = 'B23'
# 長度
n = len(text)
m = len(pattern)
# 尾端對齊。從後面往前面比對
i = m - 1       # text索引值
j = m - 1       # pattern索引值
# 備份。比對失敗時使用。使i、j回到尾端
i0 = i
j0 = j
while i < n:
    if text[i] == pattern[j]: # 比對成功
        if j == 0:
            return i
        else:
            i -= 1
            j -= 1
    else: # 比對失敗
        i = i0 + 移動格數
        j = j0
        i0 = i
return -1
從後面往前比對
QQQA23B23
A23B23
QQQA23B23
A23B23
QQQA23B23
A23B23
比對失敗
壞字符是A
QQQA23B23A23B23
pattern往右移動還沒比對的字陸續經過壞字符A
如果某個字和壞字符相同,就可以對齊
QQQA23B23
   A23B23
這是從後面往前面比對的原因
j是比對失敗位置
如果有找到相同的字移動格數 = j - 相同字的索引值
如果沒有找到相同的字移動格數 = j - (-1)
各種可能的壞字符的ascii碼
假設壞字符只有ascii
pattern的索引值
「pattern裡面:和壞字符相同的字」的索引值
# 各種可能的壞字符的數量
NO_OF_CHARS = 256
def makeBadTable(pattern):
    # 建立預設值
    badTable = [-1] * NO_OF_CHARS
    # pattern長度
    patLen = len(pattern)
    # 從索引值0開始、從左往右
    # 如果pattern有兩個以上相同的字,右邊的位置優先、覆蓋已經設的值
    for i in range(patLen - 1):
        badTable[ord(pattern[i])] = i
        # 取得「pattern裡面:和壞字符相同的字」的索引值
    return badTable
j是比對失敗位置
移動格數 = j - badTable[ord(text[i])]
移動格數有可能是負的
需要搭配好後綴規則來決定移動格數
例如:
  QQQAA3BA3
  AA3BA3
  QQQAA3BA3
  AA3BA3
  QQQAA3BA3
  AA3BA3
  比對失敗
  QQQAA3BA3
 AA3BA3
  移動格數 -1 往左移動了
從後面往前比對
QQQA23B23
A23B23
QQQA23B23
A23B23
好後綴是23
QQQA23B23A23B23
pattern往右移動pattern[-1]左邊的字陸續經過好後綴23
如果這些字和好後綴相同,就可以對齊
QQQA23B23
   A23B23
這是從後面往前面比對的原因
QQQA23B23
B23B23
比對
QQQA23B23
   B23B23
移動、對齊
QQQA23B23
   B23B23
左邊還有字
左邊的字一樣,重蹈覆轍,不能對齊,就像上次一樣
QQQA23B23
A23B23
比對
QQQA23B23
   A23B23
移動、對齊
QQQA23B23
   A23B23
左邊還有字
左邊的字不一樣,可以對齊
QQQ23B23
23B23
QQQ23B23
   23B23
移動之後
左邊沒有字,不用檢查了
QQ23B23
3B23
QQ23B23
   3B23
好後綴可能有許多字
以最右邊那一個字的位置,代表好後綴的位置
總是m - 1(pattern最後一個索引值)
與好後綴對齊的字,可能有許多字
以最右邊那一個字的位置,代表對齊的位置
可能的對齊位置:0 到 (m - 2)
每個比對失敗位置 → 好後綴長度 → 每個可能對齊位置、逐一查找
每個可能對齊位置 → 它和它左邊的那些字,可以提供多少長度範圍(給好後綴對齊用) →
根據長度範圍算出比對失敗位置
如果長度範圍沒有到達最左邊(索引值0),表示:左邊有一字比對失敗
這個位置用來「完全對齊」
只能對齊相同長度好後綴
如果長度範圍到達最左邊(索引值0),左邊內容全部都用來對齊了
這個位置除了「完全對齊」,還可以「部份對齊」
可以對齊
相同長度好後綴 (完全對齊)大於此長度好後綴 (部份對齊)無論長度範圍是哪種情況
不能用來對齊小於此長度好後綴
理由:對齊的情形 → 完全對齊(左邊還有字 - 情況1)
如果有找到對齊的位置移動格數 = (m - 1) - 對齊的位置
如果沒有找到對齊的位置,移動pattern長度移動格數 = (m - 1) - (-1)
比對失敗位置:0 到 (m-1)
移動格數
def makeGoodTable(pattern):
    # pattern長度
    patLen = len(pattern)
    # 建立預設值
    defaultMove = patLen     # (patLen - 1) - (-1)
    goodTable = [defaultMove] * patLen
    # 一開始就比對失敗,沒有好後綴,移動1格
    goodTable[-1] = 1
    # 好後綴位置
    good = patLen - 1
 
    # test是每一個可能對齊的位置
    # 從左到右,較右邊位置優先,覆蓋已經設的值
    for test in range(patLen - 1):
        t = test
        g = good
        while t > -1:
            if pattern[t] != pattern[g]:
                break
            t -= 1
            g -= 1
        '''
        比對失敗位置| 好後綴左端     好後綴右端
             g     |     g+1         good
                   |
             t     |     t+1         test
                   | 對齊範圍左端   對齊範圍右端
        '''
        # t有移動
        if t != test:
            goodTable[g] = good - test
            # t有移動,而且移到了最左邊
            # 可以用來部份對齊  比對失敗位置[0:g]
            if t == -1:
                goodTable[0:g] = [good - test] * g
    return goodTable
# 各種可能的壞字符的數量
NO_OF_CHARS = 256
def makeBadTable(pattern):
    # 建立預設值
    badTable = [-1] * NO_OF_CHARS
    # pattern長度
    patLen = len(pattern)
    # 從索引值0開始、從左往右
    # 如果pattern有兩個以上相同的字,右邊的位置優先、覆蓋已經設的值
    for i in range(patLen - 1):
        badTable[ord(pattern[i])] = i
        # 取得「pattern裡面:和壞字符相同的字」的索引值
    return badTable
def makeGoodTable(pattern):
    # pattern長度
    patLen = len(pattern)
    # 建立預設值
    defaultMove = patLen     # (patLen - 1) - (-1)
    goodTable = [defaultMove] * patLen
    # 一開始就比對失敗,沒有好後綴,移動1格
    goodTable[-1] = 1
    # 好後綴位置
    good = patLen - 1
 
    # test是每一個可能對齊的位置
    # 從左到右,較右邊位置優先,覆蓋已經設的值
    for test in range(patLen - 1):
        t = test
        g = good
        while t > -1:
            if pattern[t] != pattern[g]:
                break
            t -= 1
            g -= 1
        '''
        比對失敗位置| 好後綴左端     好後綴右端
             g     |     g+1         good
                   |
             t     |     t+1         test
                   | 對齊範圍左端   對齊範圍右端
        '''
        # t有移動
        if t != test:
            goodTable[g] = good - test
            # t有移動,而且移到了最左邊
            # 可以用來部份對齊  比對失敗位置[0:g]
            if t == -1:
                goodTable[0:g] = [good - test] * g
    return goodTable
def BM(text, pattern):
    # 長度
    n = len(text)
    m = len(pattern)
    # 尾端對齊。從後面往前面比對
    i = m - 1       # text索引值
    j = m - 1       # pattern索引值
    # 備份。比對失敗時使用。使i、j回到尾端
    i0 = i
    j0 = j
    # 製造表格
    badTable = makeBadTable(pattern)
    goodTable = makeGoodTable(pattern)
    while i < n:
        if text[i] == pattern[j]: # 比對成功
            if j == 0:
                return i
            else:
                i -= 1
                j -= 1
        else: # 比對失敗
            i = i0 + max(j - badTable[ord(text[i])], goodTable[j])
            j = j0
            i0 = i
    return -1
if __name__ == '__main__':
    # 這個版本的BM只能找ascii
    text = '49 59 69 45 55 65'
    pattern = '65'
    r = BM(text, pattern)
    print('match at %s' % r)
i值的兩種使用方式  text[i] == pattern[j]
text[i+j] == pattern[j]
理論版
badTable[比對失敗位置][ascii碼]
實際版
badTable[ascii碼]
有三種
pattern索引值(壞字符相同字所在處)
預設值:-1
移動格數
預設值:m
計算方式:m - 1 - pattern索引值
移動格數 + i值回到尾端距離
計算方式:m - 1 - pattern索引值
m - 1 - pattern索引值的意思一開始就比對失敗的移動格數各種比對失敗位置的移動格數 = 一開始就比對失敗的移動格數 - (j0 - j)
不管是在哪一個位置比對失敗,就當作是一開始就比對失敗,壞字符是text[i0]
(Boyer–Moore–Horspool algorithm)
移動格數 + i值回到尾端距離
與text[i] == pattern[j]搭配使用
原本:i = i0 + 移動格數
現在:i = i  + (移動格數 + i值回到尾端距離)
有兩種
移動格數
預設值:m
移動格數 + i值回到尾端距離
與text[i] == pattern[j]搭配使用
原本:i = i0 + 移動格數
現在:i = i  + (移動格數 + i值回到尾端距離)
表格的值常常大於pattern長度